#include <cstdio>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <curl/curl.h>

using namespace std;

struct FileInfo {
    char *fileptr;
    int offset;
};

size_t writeFunc(void *ptr, size_t size, size_t memb, void *userdata) {
    cout << "writeFunc: " << size * memb << endl;
    struct FileInfo *fileInfo = (struct FileInfo*)userdata;
    memcpy(fileInfo->fileptr + fileInfo->offset, ptr, size * memb);
    fileInfo->offset += size * memb;
    return size * memb;
}

double getDownloadFileLength(const char *url) {
    CURL *curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_HEADER, 1);
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
    CURLcode resCode = curl_easy_perform(curl);
    double downloadFileLength = 0;
    if (resCode == CURLE_OK) {
        curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
    } else {
        downloadFileLength = -1;
        cout << "getDownloadFileLength error code: " << resCode << endl;
    }
    curl_easy_cleanup(curl);
    return downloadFileLength;
}

int download(const char *url, const char *filename) {
    // get length
    double fileLength = getDownloadFileLength(url);
    cout << "fileLength: " << fileLength << endl;
    // open
    int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        cout << "fd: " << fd << endl;
        return -1;
    }
    // lseek
    if (-1 == lseek(fd, fileLength-1, SEEK_SET)) {
        perror("lseek");
        close(fd);
        return -1;
    }
    if (1 != write(fd, "", 1)) {
        perror("lseek");
        close(fd);
        return -1;
    }
    // mmap
    char *fileptr = (char*)mmap(NULL, fileLength, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
    if (fileptr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return -1;
    }
    struct FileInfo *fileInfo = (struct FileInfo*)malloc(sizeof(struct FileInfo));
    if (fileInfo == NULL) {
        munmap(fileptr, fileLength);
        close(fd);
        return -1;
    }
    fileInfo->fileptr = fileptr;
    fileInfo->offset = 0;
    cout << fileptr << "-" << fileLength << "-" << fileInfo->fileptr << "-" << fileInfo->offset << endl;
    // download
    CURL *curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fileInfo->fileptr);
    CURLcode resCode = curl_easy_perform(curl);
    if (resCode != CURLE_OK) {
        cout << "res: " << resCode << endl;
    }
    curl_easy_cleanup(curl);
    free(fileInfo);
    munmap(fileptr, fileLength);
    close(fd);
    return 0;
}

int main(int argc, const char* argv[]) {
    if (argc < 3) {
        return -1;
    }
    return download(argv[1], argv[2]);
}
